package com.jkgh.asin.buffers;

import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;


public class SimpleProducerConsumerBuffer implements ProducerConsumerBuffer {

	private byte[] buffer;
	private int readFrom;
	private int writeTo;

	public SimpleProducerConsumerBuffer() {
		this(1024*1024);
	}
	
	public SimpleProducerConsumerBuffer(int length) {
		this.buffer = new byte[length];
		this.writeTo = 0;
		this.readFrom = 0;
	}

	@Override
	public int getReadable() {
		int ret = writeTo-readFrom;
		if (ret < 0) {
			return buffer.length+ret;
		} else {
			return ret;
		}
	}
	
	@Override
	public int getWritable() {
		int ret = readFrom-writeTo-1;
		if (ret < 0) {
			return buffer.length+ret;
		} else {
			return ret;
		}
	}
	
	private void spinWriter() {
		++writeTo;
		if (writeTo == buffer.length) {
			writeTo = 0;
		}
	}
	
	private void spinWriter(int by) {
		writeTo += by;
		if (writeTo >= buffer.length) {
			writeTo -= buffer.length;
		}
	}

	private void spinReader() {
		++readFrom;
		if (readFrom == buffer.length) {
			readFrom = 0;
		}
	}
	
	private void spinReader(int by) {
		readFrom += by;
		if (readFrom >= buffer.length) {
			readFrom -= buffer.length;
		}
	}

	
	@Override
	public void consumeByte(byte b) {
		if (getWritable() < 1) {
			throw new BufferOverflowException();
		}
		buffer[writeTo] = b;
		spinWriter();
	}
	
	@Override
	public void consumeInt(int i) {
		if (getWritable() < 4) {
			throw new BufferOverflowException();
		}
		int to = writeTo;
		if (to < buffer.length-4) {
			buffer[to] = (byte) (i >>> 24);
			buffer[to+1] = (byte) (i >>> 16);
			buffer[to+2] = (byte) (i >>> 8);
			buffer[to+3] = (byte) i;
			spinWriter(4);
		} else {
			buffer[writeTo] = (byte) (i >>> 24);
			spinWriter();
			buffer[writeTo] = (byte) (i >>> 16);
			spinWriter();
			buffer[writeTo] = (byte) (i >>> 8);
			spinWriter();
			buffer[writeTo] = (byte) i;
			spinWriter();
		}
	}

	@Override
	public void consumeDouble(double d) {
		if (getWritable() < 8) {
			throw new BufferOverflowException();
		}
		long l = Double.doubleToLongBits(d);
		int to = writeTo;
		if (to < buffer.length-8) {
			buffer[to] = (byte) (l >>> 56);
			buffer[to+1] = (byte) (l >>> 48);
			buffer[to+2] = (byte) (l >>> 40);
			buffer[to+3] = (byte) (l >>> 32);
			buffer[to+4] = (byte) (l >>> 24);
			buffer[to+5] = (byte) (l >>> 16);
			buffer[to+6] = (byte) (l >>> 8);
			buffer[to+7] = (byte) l;
			spinWriter(8);
		} else {
			buffer[writeTo] = (byte) (l >>> 56);
			spinWriter();
			buffer[writeTo] = (byte) (l >>> 48);
			spinWriter();
			buffer[writeTo] = (byte) (l >>> 40);
			spinWriter();
			buffer[writeTo] = (byte) (l >>> 32);
			spinWriter();
			buffer[writeTo] = (byte) (l >>> 24);
			spinWriter();
			buffer[writeTo] = (byte) (l >>> 16);
			spinWriter();
			buffer[writeTo] = (byte) (l >>> 8);
			spinWriter();
			buffer[writeTo] = (byte) l;
			spinWriter();
		}
	}

	@Override
	public void consumeBytes(byte[] bs, int offset, int length) {
		if (getWritable() < length) {
			throw new BufferOverflowException();
		}
		if (writeTo < buffer.length-length) {
			System.arraycopy(bs, offset, buffer, writeTo, length);
			spinWriter(length);
		} else {
			int partLength = buffer.length-writeTo;
			System.arraycopy(bs, offset, buffer, writeTo, partLength);
			System.arraycopy(bs, offset+partLength, buffer, 0, length-partLength);
			spinWriter(length);
		}
	}

	@Override
	public void consumeBytes(ByteBuffer bb, int length) {
		if (getWritable() < length) {
			throw new BufferOverflowException();
		}
		if (writeTo < buffer.length-length) {
			bb.get(buffer, writeTo, length);
			spinWriter(length);
		} else {
			int partLength = buffer.length-writeTo;
			bb.get(buffer, writeTo, partLength);
			bb.get(buffer, 0, length-partLength);
			spinWriter(length);
		}
	}
	
	@Override
	public byte produceByte() {
		if (getReadable() < 1) {
			throw new BufferUnderflowException();
		}
		byte ret = buffer[readFrom];
		spinReader();
		return ret;
	}

	@Override
	public int produceInt() {
		if (getReadable() < 4) {
			throw new BufferUnderflowException();
		}
		int from = readFrom;
		int ret = (buffer[from] & 255) << 24;
		if (from < buffer.length-4) {
			ret += (buffer[from+1] & 255) << 16;
			ret += (buffer[from+2] & 255) << 8;
			ret += buffer[from+3] & 255;
			spinReader(4);
		} else {
			spinReader();
			ret += (buffer[readFrom] & 255) << 16;
			spinReader();
			ret += (buffer[readFrom] & 255) << 8;
			spinReader();
			ret += buffer[readFrom] & 255;
			spinReader();
		}
		return ret;
	}

	@Override
	public double produceDouble() {
		if (getReadable() < 8) {
			throw new BufferUnderflowException();
		}
		int from = readFrom;
		long ret = (buffer[from] & 255) << 56;
		if (from < buffer.length-8) {
			ret += (buffer[from+1] & 255) << 48;
			ret += (buffer[from+2] & 255) << 40;
			ret += (buffer[from+3] & 255) << 32;
			ret += (buffer[from+4] & 255) << 24;
			ret += (buffer[from+5] & 255) << 16;
			ret += (buffer[from+6] & 255) << 8;
			ret += buffer[from+7] & 255;
			spinReader(8);
		} else {
			spinReader();
			ret += (buffer[readFrom] & 255) << 48;
			spinReader();
			ret += (buffer[readFrom] & 255) << 40;
			spinReader();
			ret += (buffer[readFrom] & 255) << 32;
			spinReader();
			ret += (buffer[readFrom] & 255) << 24;
			spinReader();
			ret += (buffer[readFrom] & 255) << 16;
			spinReader();
			ret += (buffer[readFrom] & 255) << 8;
			spinReader();
			ret += buffer[readFrom] & 255;
			spinReader();
		}
		return Double.longBitsToDouble(ret);
	}

	@Override
	public void produceBytes(byte[] bs, int offset, int length) {
		if (getReadable() < length) {
			throw new BufferUnderflowException();
		}
		if (readFrom < buffer.length-length) {
			System.arraycopy(buffer, readFrom, bs, offset, length);
			spinReader(length);
		} else {
			int partLength = buffer.length-readFrom;
			System.arraycopy(buffer, readFrom, bs, offset, partLength);
			System.arraycopy(buffer, 0, bs, offset+partLength, length-partLength);
			spinReader(length);
		}
	}

	@Override
	public void produceBytes(ByteBuffer bb, int length) {
		if (getReadable() < length) {
			throw new BufferUnderflowException();
		}
		if (readFrom < buffer.length-length) {
			bb.put(buffer, readFrom, length);
			spinReader(length);
		} else {
			int partLength = buffer.length-readFrom;
			bb.put(buffer, readFrom, partLength);
			bb.put(buffer, 0, length-partLength);
			spinReader(length);
		}
	}

	@Override
	public void revokeBytes(int length) {
		if (getWritable() < length) {
			throw new BufferOverflowException();
		}
		readFrom -= length;
		if (readFrom < 0) {
			readFrom += buffer.length;
		}
	}	
	
}
